/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Text.h"
#include <lib3d/hard/Hardware.h>
#include <xml/Stream.h>
#include <xml/Saver.h>
#include <Converter.h>
#include <SimpleArray.h>
#include <resource/Manager.h>
#include <text/Lookup.h>
#include "font/FontVisitor.h"
#include "font/AbstractFont.h"
#include "ProportionalTextImpl.h"
#include "TrueTypeTextImpl.h"
#include "SymbolAtlasTextImpl.h"
#include "TextRenderInfo.h"
#include <system/assert.h>

#include <boost/bind.hpp>

#ifdef IPHONE_VERSION
extern std::wstring StringToWString(const std::string & string);
#endif

namespace liba {
namespace lib3d {
namespace node {

namespace {

class ImplCreatingVisitor
	: public font::FontVisitor
{
public:
	virtual void Visit(font::Proportional* font)
	{
		createdImpl.reset(new ProportionalTextImpl(font));
	}
#ifndef NO_TRUETYPE
	virtual void Visit(font::TrueType* font)
	{
		createdImpl.reset(new TrueTypeTextImpl(font));
	}
#endif
	virtual void Visit(liba::lib3d::node::text::SymbolAtlas* font)
	{
		createdImpl.reset(new SymbolAtlasTextImpl(font));
	}

	std::auto_ptr<TextImpl>& Created() { return createdImpl; }
private:
	std::auto_ptr<TextImpl> createdImpl;
};

}

converter::EnumPair<char, TextData::Alignment> Text::AlignmentNames[] = 
{
	converter::MakeEnumPair( "LEFT", TextData::LEFT ),
	converter::MakeEnumPair( "CENTER", TextData::CENTER ),
	converter::MakeEnumPair( "RIGHT", TextData::RIGHT ),
	0
};

const Atom Text::node_name( "Text" );
const Atom Text::text_name( "Text" );
const Atom Text::font_name( "Font" );
const Atom Text::alignment_name( "Alignment" );
const Atom Text::valignment_name( "VAlignment" );
const Atom Text::COLOR_NAME("Color");
const Atom Text::TRANSPARENCY_NAME("Transparency");

Text::Text(const Atom & name, Node * parent)
	: Node( name, parent )
	, color(Color(1, 1, 1))
	, transparency(0.0001f)
{
	this->text.Changed().connect(boost::bind(&Text::TextChanged, this, _1));
	this->RegisterProperty(text_name, &this->text);

	this->font.Changed().connect(boost::bind(&Text::FontChanged, this, _1));
	this->RegisterProperty(font_name, &this->font);

	this->RegisterProperty(COLOR_NAME, &this->color);

	node_type = node_name;
}

Text::Text(const Text & other) 
	: Node(other)
	, textData(other.textData)
	, impl(other.impl->clone())
	, color(other.color)
	, transparency(other.transparency)
{
	this->text.Changed().connect(boost::bind(&Text::TextChanged, this, _1));
	this->RegisterProperty(text_name, &this->text);

	this->font.Changed().connect(boost::bind(&Text::FontChanged, this, _1));
	this->RegisterProperty(font_name, &this->font);

	this->RegisterProperty(COLOR_NAME, &this->color);

	impl->SetTextData(&textData);
	textDataChanged();
}
	
Text::~Text()
{
}

Text * Text::Clone()
{
	return new Text(*this);
}

bool Text::on_attribute( xml::Provider * prov, const std::wstring & name, const std::wstring & value )
{
	if( name == L"Text" )
	{
		std::wstring textValue;
		if (converter::convert( value, &textValue ))
		{
			textData.SetText(textValue);
			textDataChanged();
			return true;
		}
		return false;
	}

	return Text::on_attribute( prov, std::string( name.begin(), name.end() ), std::string( value.begin(), value.end() ) );
}

bool Text::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if( name == "Font" )
	{
		load_rep( value );
		fontFile = value.c_str();
		return true;
	}
	if( name == "Text" )
	{
#ifndef IPHONE_VERSION
		std::wstring textValue;
		if (converter::convert(value, &textValue))
#else
		std::wstring textValue = StringToWString(value);
#endif
		this->change_text(textValue, true);
		return true;
	}
	if (name == "TextId")
	{
		this->change_text(liba::text::Lookup(value), true);

		textID = value;

		return true;
	}
	if( name == "Alignment" )
	{
		TextData::Alignment alignmentValue;
		if (converter::convert(value, &alignmentValue))
		{
			textData.SetAlignment(alignmentValue);
			textDataChanged();
			return true;
		}
		return false;
	}
	if (name == "VAlignment")
	{
		VAlignment valignment;
		if (converter::convert(value, &valignment))
		{
			textData.SetValignment(valignment);
			textDataChanged();
			return true;
		}
		return false;
	}
	if( name == "Normalize")
	{
		bool normalizeValue;
		if (converter::convert(value, &normalizeValue))
		{
			textData.SetNormalize(normalizeValue);
			textDataChanged();
			return true;
		}
		return false;
	}
	if( name == "MaxWidth")
	{
		int maxWidth;
		if (converter::convert(value, &maxWidth))
		{
			textData.SetMaxWidth(maxWidth);
			textDataChanged();
			return true;
		}
		return false;
	}
	if(name == "Color")
	{
		Color convertedValue;
		if (!converter::convert(value, &convertedValue))
			return false;
		this->color = convertedValue;
	}
	if(name == "Transparency")
	{
		float convertedValue;
		if (!converter::convert(value, &convertedValue))
			return false;
		this->transparency = convertedValue;
	}

	return Node::on_attribute( prov, name, value );
}

void Text::change_alignment(Alignment alignmentValue)
{
	textData.SetAlignment((TextData::Alignment)alignmentValue);
	textDataChanged();
}

Text::Alignment Text::get_alignment()const
{
	return (Text::Alignment)textData.GetAlignment();
}

void Text::on_save_attributes( xml::SaverBase * saver )const
{
	Node::on_save_attributes(saver);

	saver->save_attribute("Font", get_rep_name());

	if(!textID.empty())
	{
		saver->save_attribute("TextId", textID);
	}
	else if(!get_text().empty())
	{
		saver->save_attribute("Text", get_text());
	}

	if(textData.GetAlignment() != TextData::LEFT)
		saver->save_attribute("Alignment", textData.GetAlignment());

	if(textData.GetNormalize() != true)
		saver->save_attribute("Normalize", textData.GetNormalize());

	if(color.Get() != Color(1, 1, 1))
		saver->save_attribute("Color", color.Get());

	if(transparency > 0.0001f)
		saver->save_attribute("Transparency", transparency.Get());
}

void Text::on_save_nodes( xml::SaverBase * saver )const
{
	Node::on_save_nodes(saver);
}

void Text::change_font(const filesystem::String & path)
{
	load_rep(path);
	
	ImplCreatingVisitor visitor;
	get_rep()->GetAbstractFont()->Accept(&visitor);

	impl = visitor.Created();
	impl->SetTextData(&textData);
	textDataChanged();

	fontFile = path;
}

filesystem::String Text::get_font() const
{
	return fontFile;
}

void Text::TextChanged(const std::wstring& value)
{
	textData.SetText(value);
	textDataChanged();
}

void Text::FontChanged(const std::wstring& value)
{
	load_rep( value );
	
	ImplCreatingVisitor v;
	get_rep()->GetAbstractFont()->Accept(&v);

	impl = v.Created();
	impl->SetTextData(&textData);
	textDataChanged();

	fontFile = value.c_str();
}

bool Text::on_end_node( xml::Provider * prov )
{
	ImplCreatingVisitor v;
	get_rep()->GetAbstractFont()->Accept(&v);
	
	impl = v.Created();
	impl->SetTextData(&textData);
	textDataChanged();

	return true;
}

void Text::RenderToList( hard::Hardware * hardware, hard::RenderList * render_list )
{
	if(impl.get() == NULL)
		return;

	TextRenderInfo tri;
	tri.color = color;
	tri.transparency = transparency;
	tri.total_matrix = GetGlobalMatrix();
	impl->render_to_list(hardware, &tri, render_list);
	return Node::RenderToList( hardware, render_list );
}

void Text::change_text(const std::string & text, bool parseEscapes)
{
	std::wstring textCopy;
	converter::convert( text, &textCopy );
	change_text( textCopy, parseEscapes);
}

void Text::change_text(const std::wstring & text, bool parseEscapes)
{
	std::wstring textCopy = text;
	if (parseEscapes)
	{
		// replace \n and \t patterns in string with new line and tab symbols
		int patternPosition = 0;
		while((patternPosition = textCopy.find(L"\\n")) != textCopy.npos)
			textCopy.replace(patternPosition, 2, L"\n");
		
		while((patternPosition = textCopy.find(L"\\t")) != textCopy.npos)
			textCopy.replace(patternPosition, 2, L"\t");
	}

	textData.SetText(textCopy);
	textDataChanged();
}

const std::wstring & Text::get_text()const
{
	return textData.GetText();
}
	
float Text::get_text_width(const std::wstring & text)
{
	if(impl.get() == NULL)
		return 0;

	return impl->get_text_width(text);
}
	
float Text::get_text_height()
{
	if(impl.get() == NULL)
		return 0;

	return impl->get_text_height();
}

void Text::textDataChanged()
{
	if (impl.get())
		impl->text_data_changed();
}

void Text::change_max_width(int value)
{
	textData.SetMaxWidth(value);
	textDataChanged();
}

void Text::CreateProxy(ProxyVisitor * proxyVisitor)
{
	proxyVisitor->CreateTextProxy(this);
}

} // namespace node
} // namespace lib3d
} // namespace liba

